बैकएंड सेवाओं को विकसित करते समय, यदि डेटाबेस एकीकरण गलत तरीके से लागू किया जाता है, तो समस्याएँ पैदा करना आसान है। यह लेख आपको आधुनिक सेवाओं में रिलेशनल डेटाबेस के साथ काम करने के लिए कुछ सर्वोत्तम अभ्यास बताएगा और आपको यह भी दिखाएगा कि स्वचालित रूप से अप-टू-डेट स्कीमा बनाना और रखना एक अच्छा विचार नहीं है।
मैं डेटाबेस माइग्रेशन के लिए फ्लाईवे , आसान सेटअप के लिए स्प्रिंग बूट और उदाहरण डेटाबेस के रूप में H2 का उपयोग करूंगा।
मैंने इस बारे में बुनियादी जानकारी नहीं दी कि माइग्रेशन क्या हैं और वे कैसे काम करते हैं। यहाँ फ्लाईवे के अच्छे लेख हैं:
बहुत समय पहले, डेवलपर्स एप्लिकेशन से अलग स्क्रिप्ट लागू करके डेटाबेस को इनिशियलाइज़ और अपडेट कर रहे थे। हालांकि, इन दिनों कोई भी ऐसा नहीं करता है क्योंकि इसे विकसित करना और एक उचित स्थिति में बनाए रखना कठिन है, जिससे गंभीर परेशानी होती है।
आजकल, डेवलपर्स ज्यादातर दो दृष्टिकोणों का उपयोग करते हैं:
स्वचालित पीढ़ी, उदाहरण के लिए, जेपीए या हाइबरनेट - डेटाबेस शुरू होता है और कक्षाओं और वर्तमान डीबी स्थिति की तुलना करके अद्यतित रहता है; यदि परिवर्तन की आवश्यकता है, तो वे लागू होते हैं।
डेटाबेस माइग्रेशन - डेवलपर्स डेटाबेस को क्रमिक रूप से अपडेट करते हैं, और स्टार्टअप, डेटाबेस माइग्रेशन पर परिवर्तन स्वचालित रूप से लागू होते हैं।
इसके अलावा, अगर हम स्प्रिंग के बारे में बात करते हैं, तो बॉक्स से बाहर एक बुनियादी डेटाबेस आरंभीकरण है, लेकिन यह फ्लाईवे या लिक्विबेस जैसे इसके एनालॉग्स की तुलना में कम उन्नत है।
यह प्रदर्शित करने के लिए कि यह कैसे काम करता है आइए एक साधारण उदाहरण का उपयोग करें। तीन फ़ील्ड वाले टेबल उपयोगकर्ता - id
, user_name
, email
:
आइए हाइबरनेट द्वारा स्वचालित रूप से उत्पन्न एक पर एक नज़र डालें।
हाइबरनेट इकाई:
@Entity @Table(name = "users") public class User { @Id @GeneratedValue private UUID id; @Column(name = "user_name", length = 64, nullable = false) private String userName; @Column(name = "email", length = 128, nullable = true) private String email; }
स्कीमा को अद्यतित रखने के लिए हमें स्प्रिंग बूट कॉन्फ़िगरेशन में इस पंक्ति की आवश्यकता है और यह स्टार्टअप पर करना शुरू कर देता है:
jpa.hibernate.ddl-auto=update
और आवेदन शुरू होने पर हाइबरनेट से लॉग इन करें:
Hibernate: create table users (id binary(255) not null, email varchar(128), user_name varchar(64) not null, primary key (id))
स्वचालित जनरेटिंग के बाद, इसने 255 के अधिकतम आकार के साथ binary
के रूप में id
बनाई, यह बहुत अधिक है क्योंकि UUID
में केवल 36 वर्ण होते हैं। तो हमें इसके बजाय UUID
प्रकार का उपयोग करने की आवश्यकता है, हालांकि, यह इस तरह से उत्पन्न नहीं होता है। इस एनोटेशन को जोड़कर इसे ठीक किया जा सकता है:
@Column(name = "id", columnDefinition = "uuid")
हालाँकि, हम पहले से ही कॉलम में SQL परिभाषा लिख रहे हैं, जो SQL से Java तक एब्स्ट्रैक्शन को तोड़ता है।
और आइए कुछ उपयोगकर्ताओं के साथ तालिका भरें:
insert into users (id, user_name, email) values ('297a848d-d406-4055-8a6f-4a4118a44001', 'Artem', null); insert into users (id, user_name, email) values ('921a9d42-bf14-4c3f-9893-60f79cdd0825', 'Antonio', '[email protected]');
उदाहरण के लिए, आइए कल्पना करें कि कुछ समय बाद हम अपने ऐप में नोटिफिकेशन जोड़ना चाहते हैं, और इसके परिणामस्वरूप ट्रैक करना चाहते हैं कि कोई उपयोगकर्ता उन्हें प्राप्त करना चाहता है या नहीं। इसलिए हमने तालिका उपयोगकर्ताओं के लिए एक कॉलम receive_notifications
जोड़ने और इसे गैर-शून्य बनाने का निर्णय लिया।
इसका मतलब है कि हाइबरनेट इकाई में, हम नया कॉलम जोड़ते हैं:
@Column(name = "receive_notifications", nullable = false) private Boolean receiveNotifications;
ऐप शुरू करने के बाद, हम लॉग में त्रुटि देखते हैं और कोई नया कॉलम नहीं देखते हैं। ऐसा इसलिए है क्योंकि तालिका खाली नहीं है, और हमें मौजूदा पंक्तियों के लिए एक डिफ़ॉल्ट मान सेट करने की आवश्यकता है:
Error executing DDL "alter table users add column receive_notifications boolean not null" via JDBC Statement
हम फिर से SQL कॉलम परिभाषा जोड़कर एक डिफ़ॉल्ट मान सेट कर सकते हैं:
columnDefinition = "boolean default true"
और हाइबरनेट लॉग से, हम देख सकते हैं कि इसने काम किया:
Hibernate: alter table users add column receive_notifications boolean default true not null
हालांकि, आइए कल्पना करें कि हमें कुछ अधिक जटिल होने के लिए receive_notifications
की आवश्यकता है, उदाहरण के लिए, सही या गलत, इस पर निर्भर करता है कि ईमेल भरा है या नहीं। उस तर्क को केवल हाइबरनेट के साथ लागू करना असंभव है, इसलिए हमें वैसे भी माइग्रेशन की आवश्यकता है।
संक्षेप में, स्वचालित रूप से उत्पन्न और अद्यतन स्कीमा दृष्टिकोण की मुख्य कमियां:
यह जावा-प्रथम है और फलस्वरूप SQL के संदर्भ में लचीला नहीं है, गैर-अनुमानित, पहले जावा पर उन्मुख है, और कभी-कभी SQL सामग्री को आपकी अपेक्षा के अनुरूप नहीं करता है। आप इसे संचालित करने के लिए कुछ SQL परिभाषाएँ लिख सकते हैं, लेकिन यह शुद्ध SQL DDL की तुलना में सीमित है।
कभी-कभी मौजूदा तालिकाओं को अपडेट करना और डेटा के साथ कुछ करना असंभव होता है, और हमें वैसे भी SQL स्क्रिप्ट की आवश्यकता होती है। ज्यादातर मामलों में, यह स्वचालित स्कीमा अद्यतन और डेटा अद्यतन करने के लिए माइग्रेशन रखने के साथ समाप्त होता है। माइग्रेशन में डेटाबेस लेयर से संबंधित हर चीज को स्वचालित रूप से जेनरेट करने और करने से बचना हमेशा आसान होता है।
साथ ही, जब समानांतर विकास की बात आती है तो यह सुविधाजनक नहीं है क्योंकि यह वर्जनिंग का समर्थन नहीं करता है, और यह बताना मुश्किल है कि स्कीमा के साथ क्या हो रहा है।
स्कीमा को स्वचालित रूप से जेनरेट और अपडेट किए बिना यह कैसा दिखता है:
डीबी प्रारंभ करने के लिए स्क्रिप्ट:
संसाधन/डीबी/माइग्रेशन/V1__db_initialization.sql
create table if not exists users ( id uuid not null primary key, user_name varchar(64) not null, email varchar(128) );
कुछ उपयोगकर्ताओं के साथ डेटाबेस भरना:
संसाधन/डीबी/माइग्रेशन/V2__users_some_data.sql
insert into users (id, user_name, email) values ('297a848d-d406-4055-8a6f-4a4118a44001', 'Artem', null); insert into users (ID, USER_NAME, EMAIL) values ('921a9d42-bf14-4c3f-9893-60f79cdd0825', 'Antonio', '[email protected]');
नया फ़ील्ड जोड़ना और मौजूदा पंक्तियों में गैर-तुच्छ डिफ़ॉल्ट मान सेट करना:
संसाधन/डीबी/माइग्रेशन/V3__users_add_receive_notification.sql
alter table users add column if not exists receive_notifications boolean; -- It's not a really safe with huge amount of data but good for the example update users set users.receive_notifications = email is not null; alter table users alter column receive_notifications set not null;
और अगर हम चुनते हैं तो कुछ भी हमें हाइबरनेट का उपयोग करने से नहीं रोकता है। कॉन्फ़िगरेशन में, हमें यह संपत्ति सेट करने की आवश्यकता है:
jpa.hibernate.ddl-auto=validate
अब हाइबरनेट कुछ भी उत्पन्न नहीं करेगा। यह केवल जांच करेगा कि जावा प्रतिनिधित्व डीबी से मेल खाता है या नहीं। इसके अलावा, हमें हाइबरनेट स्वचालित जनरेट करने के लिए कुछ जावा और एसक्यूएल को मिलाने की आवश्यकता नहीं है, इसलिए यह संक्षिप्त और अतिरिक्त जिम्मेदारी के बिना हो सकता है:
@Entity @Table(name = "users") public class User { @Id @Column(name = "id") @GeneratedValue private UUID id; @Column(name = "user_name", length = 64, nullable = false) private String userName; @Column(name = "email", length = 128, nullable = true) private String email; @Column(name = "receive_notifications", nullable = false) private Boolean receiveNotifications; }
if exists
if not exists
/ मौजूद है जैसे चेक जोड़कर आसानी से प्राप्त किया जा सकता है जैसा कि हमने ऊपर किया था।V{datetime}__description.sql
का उपयोग करने के बजाय माइग्रेशन नामकरण के लिए V{version+=1}__description.sql
पैटर्न का उपयोग करना बेहतर है। दूसरा सुविधाजनक है और समानांतर विकास में संस्करण संख्या संघर्ष से बचने में मदद करेगा। लेकिन कभी-कभी, संस्करणों को नियंत्रित किए बिना डेवलपर्स के सफलतापूर्वक माइग्रेशन लागू करने की तुलना में नाम का विरोध होना बेहतर है।
यह बहुत सारी जानकारी थी, लेकिन मुझे आशा है कि आपको यह मददगार लगेगी। यदि आप स्कीमा को स्वचालित रूप से जेनरेट/अपडेट करने का उपयोग करते हैं - स्कीमा के साथ क्या हो रहा है, इस पर एक नज़र डालें क्योंकि यह अप्रत्याशित व्यवहार कर सकता है। और इसे संचालित करने के लिए जितना संभव हो उतना विवरण जोड़ना हमेशा एक अच्छा विचार है।
लेकिन अगली बार माइग्रेशन पर विचार करना बेहतर होगा क्योंकि यह जावा संस्थाओं को राहत देगा, अतिरिक्त जिम्मेदारी को हटा देगा, और डीडीएल पर बहुत अधिक नियंत्रण के साथ आपको लाभान्वित करेगा।
सर्वोत्तम प्रथाओं को सारांशित करने के लिए:
आप GitHub पर पूरी तरह से काम करने वाला उदाहरण पा सकते हैं।